/*
 *  Description: iOS 12 SSL Bypass based on blog post https://nabla-c0d3.github.io/blog/2019/05/18/ssl-kill-switch-for-iOS12/
 *  Author:     @macho_reverser
 */

// Variables
var SSL_VERIFY_NONE = 0;
var ssl_ctx_set_custom_verify;
var ssl_get_psk_identity;

/* Create SSL_CTX_set_custom_verify NativeFunction
 *  Function signature https://github.com/google/boringssl/blob/7540cc2ec0a5c29306ed852483f833c61eddf133/include/openssl/ssl.h#L2294
 */
ssl_ctx_set_custom_verify = new NativeFunction(
    Module.findExportByName("libboringssl.dylib", "SSL_CTX_set_custom_verify"),
    'void', ['pointer', 'int', 'pointer']
);

/* Create SSL_get_psk_identity NativeFunction
 * Function signature https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_get_psk_identity
 */
ssl_get_psk_identity = new NativeFunction(
    Module.findExportByName("libboringssl.dylib", "SSL_get_psk_identity"),
    'pointer', ['pointer']
);

function custom_verify_callback_that_does_not_validate(ssl, out_alert) {
    return SSL_VERIFY_NONE;
}

var ssl_verify_result_t = new NativeCallback(function (ssl, out_alert) {
    custom_verify_callback_that_does_not_validate(ssl, out_alert);
}, 'int', ['pointer', 'pointer']);

function bypassSSL() {
    console.log("[+] Bypass successfully loaded ");

    Interceptor.replace(ssl_ctx_set_custom_verify, new NativeCallback(function (ssl, mode, callback) {
        ssl_ctx_set_custom_verify(ssl, mode, ssl_verify_result_t);
    }, 'void', ['pointer', 'int', 'pointer']));

    Interceptor.replace(ssl_get_psk_identity, new NativeCallback(function (ssl) {
        return "notarealPSKidentity";
    }, 'pointer', ['pointer']));

}

function hook_stat(is_pass) {
    var stat = Module.findExportByName('libSystem.B.dylib', 'stat');
    Interceptor.attach(stat, {
        onEnter: function (args) {
            // 这里是方法被调用时的处理逻辑
            // args[0] 是 stat 方法的第一个参数，通常是文件路径
            // args[1] 是 stat 方法的第二个参数，这里可以添加其他参数的处理
            console.log('stat is hooked: ');
        },
        onLeave: function (retval) {
            if (is_pass) {
                retval.replace(-1);
                console.log(`stat retval: ${Number(retval.toString())} -> -1`);
            }
        }
    });
}

function hook_dyld_get_image_name(is_pass) {
    let cheek_paths = [
        "/Library/MobileSubstrate/MobileSubstrate.dylib",
    ]

    let NSString = ObjC.classes.NSString;
    let true_path = NSString.stringWithString_("/System/Library/Frameworks/Intents.framework/Intents");

    let _dyld_get_image_name = Module.findExportByName(null, "_dyld_get_image_name");
    Interceptor.attach(_dyld_get_image_name, {
        onEnter: function (args) {

            console.log("_dyld_get_image_name is hooked.")
            this.idx = eval(args[0]).toString(10);

        },
        onLeave: function (retval) {
            let rtnStr = retval.readCString();

            if (is_pass) {
                for (let i = 0; i < cheek_paths.length; i++) {

                    if (cheek_paths[i] === rtnStr.toString()) {
                        retval.replace(true_path);
                        console.log(`replace: (${this.idx}) ${rtnStr} => ${true_path}`)
                    }
                }

            }

        }
    })

}

function hook_canopenurl(is_pass) {

    let api = new ApiResolver("objc");
    api.enumerateMatches("-[UIApplication canOpenURL:]").forEach((matche) => {

        console.log("canOpenURL is hooked.");

        if (is_pass) {
            Interceptor.replace(matche.address, new NativeCallback((url_obj) => {
                return 0;
            }, "int", ["pointer"]))
        }
    })

}

// -[NSFileManager fileExistsAtPath:isDirectory:]
function hook_fileExistsAtPath(is_pass) {

    let api = new ApiResolver("objc");
    let matches = api.enumerateMatches("-[NSFileManager fileExistsAtPath:isDirectory:]")
    matches.forEach((matche) => {

        console.log("fileExistsAtPath is hooked.");

        if (is_pass) {
            Interceptor.replace(matche.address, new NativeCallback((path, is_dir) => {
                console.log(ObjC.Object(path).toString(), is_dir)
                return 0;
            }, "int", ["pointer", "bool"]))
        }

    })

}

function hook_writeToFile(is_pass) {

    let api = new ApiResolver("objc");
    api.enumerateMatches("-[NSString writeToFile:atomically:encoding:error:]").forEach((matche) => {

        Interceptor.attach(matche.address, {

            onEnter: function (args) {
                this.error = args[5];
                this.path = ObjC.Object(args[2]).toString();
                console.log("writeToFile is hooked");
            },
            onLeave: function (retval) {
                if (is_pass) {
                    let err = ObjC.classes.NSError.alloc();
                    Memory.writePointer(this.error, err);
                }
            }

        })

    })

}

function hook_lstat(is_pass) {
    var stat = Module.findExportByName('libSystem.B.dylib', 'lstat');
    Interceptor.attach(stat, {
        onEnter: function (args) {

            console.log('lstat is hooked: ');
        },
        onLeave: function (retval) {
            if (is_pass) {
                retval.replace(1);
                console.log(`lstat retval: ${Number(retval.toString())} -> 1`);
            }
        }
    });
}

function hook_fork(is_pass) {

    let fork = Module.findExportByName(null, "fork");
    if (fork) {
        console.log("fork is hooked.");
        Interceptor.attach(fork, {
            onLeave: function (retval) {
                console.log(`fork -> pid:${retval}`);
                if (is_pass) {
                    retval.replace(-1)
                }
            }
        })
    }

}

function hook_NSClassFromString(is_pass) {

    let clses = ["HBPreferences"];

    var foundationModule = Process.getModuleByName('Foundation');
    var nsClassFromStringPtr = Module.findExportByName(foundationModule.name, 'NSClassFromString');

    if (nsClassFromStringPtr) {
        Interceptor.attach(nsClassFromStringPtr, {
            onEnter: function (args) {
                this.cls = ObjC.Object(args[0])
                console.log("NSClassFromString is hooked");
            },
            onLeave: function (retval) {

                if (is_pass) {
                    clses.forEach((ck_cls) => {

                        if (this.cls.toString().indexOf(ck_cls) !== -1) {
                            console.log(`nsClassFromStringPtr -> ${this.cls} - ${ck_cls}`)
                            retval.replace(ptr(0x00))
                        }
                    })

                }

            }
        })

    }

}

function hook_getenv(is_pass) {

    let getenv = Module.findExportByName(null, "getenv");

    Interceptor.attach(getenv, {
        onEnter: function (args) {
            console.log("getenv is hook");
            this.env = ObjC.Object(args[0]).toString();
        },
        onLeave: function (retval) {
            if (is_pass && this.env == "DYLD_INSERT_LIBRARIES") {
                console.log(`env: ${this.env} - ${retval.readCString()}`)

                retval.replace(ptr(0x0))

            }

        }
    })

}
// Disables SSL pinning by replacing functions with no-ops.
function unpin() {
  var SecTrustEvaluate_handle = Module.findExportByName('Security', 'SecTrustEvaluate');
  var SecTrustEvaluateWithError_handle = Module.findExportByName('Security', 'SecTrustEvaluateWithError');
  var SSL_CTX_set_custom_verify_handle = Module.findExportByName('libboringssl.dylib', 'SSL_CTX_set_custom_verify');
  var SSL_get_psk_identity_handle = Module.findExportByName('libboringssl.dylib', 'SSL_get_psk_identity');
  var boringssl_context_set_verify_mode_handle = Module.findExportByName('libboringssl.dylib', 'boringssl_context_set_verify_mode');

  if (SecTrustEvaluateWithError_handle) {
      var SecTrustEvaluateWithError = new NativeFunction(SecTrustEvaluateWithError_handle, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          SecTrustEvaluateWithError_handle,
          new NativeCallback(function (trust, error) {

              console.log('[*] Called SecTrustEvaluateWithError()');
              SecTrustEvaluateWithError(trust, NULL);
              Memory.writeU8(error, 0);
              return 1;
          }, 'int', ['pointer', 'pointer'])
      );

      console.log('[+] SecTrustEvaluateWithError() hook installed.');
  }

  if (SecTrustEvaluate_handle) {
      var SecTrustEvaluate = new NativeFunction(SecTrustEvaluate_handle, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          SecTrustEvaluate_handle, new NativeCallback(function (trust, result) {
              console.log('[*] Called SecTrustEvaluate()');
              SecTrustEvaluate(trust, result);
              Memory.writeU8(result, 1);
              return 0;
          }, 'int', ['pointer', 'pointer'])
      );
      console.log('[+] SecTrustEvaluate() hook installed.');
  }

  if (SSL_CTX_set_custom_verify_handle) {
      var SSL_CTX_set_custom_verify = new NativeFunction(SSL_CTX_set_custom_verify_handle, 'void', ['pointer', 'int', 'pointer']);

      var replaced_callback = new NativeCallback(function (ssl, out) {
          console.log('[*] Called custom SSL verifier')
          return 0;
      }, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          SSL_CTX_set_custom_verify_handle,
          new NativeCallback(function (ctx, mode, callback) {
              console.log('[*] Called SSL_CTX_set_custom_verify()');
              SSL_CTX_set_custom_verify(ctx, 0, replaced_callback);
          }, 'int', ['pointer', 'int', 'pointer'])
      );

      console.log('[+] SSL_CTX_set_custom_verify() hook installed.')
  }

  if (SSL_get_psk_identity_handle) {
      Interceptor.replace(
          SSL_get_psk_identity_handle,
          new NativeCallback(function (ssl) {
              console.log('[*] Called SSL_get_psk_identity_handle()');
              return 'notarealPSKidentity';
          }, 'pointer', ['pointer'])
      );

      console.log('[+] SSL_get_psk_identity() hook installed.')
  }

  if (boringssl_context_set_verify_mode_handle) {
      var boringssl_context_set_verify_mode = new NativeFunction(boringssl_context_set_verify_mode_handle, 'int', ['pointer', 'pointer']);

      Interceptor.replace(
          boringssl_context_set_verify_mode_handle,
          new NativeCallback(function (a, b) {
              console.log('[*] Called boringssl_context_set_verify_mode()');
              return 0;
          }, 'int', ['pointer', 'pointer'])
      );

      console.log('[+] boringssl_context_set_verify_mode() hook installed.')
  }
}
/*
    https://kov4l3nko.github.io/blog/2018-05-27-sll-pinning-hook-sectrustevaluate/

	****************************************
	 killSSL.js Frida script
	 by Dima Kovalenko
	****************************************

	Usage:

		1. Run Viber on the device

		2. Inject the script to the process:
			$ frida -U -n Viber  -l path/to/killSSL.js

		3. SSL pinning in Viber HTTPs is
		   disabled. Now you can intercept
		   Viber HTTPs requests, e.g. with
		   mitmproxy.
*/

function disable_SecTrustEvaluate() {
    // Are we debugging it?
    let DEBUG = true;

	// Get SecTrustEvaluate address
	var SecTrustEvaluate_prt = Module.findExportByName("Security", "SecTrustEvaluate");
	if (SecTrustEvaluate_prt == null) {
		console.log("[!] Security!SecTrustEvaluate(...) not found!");
		return;
	}

	// Create native function wrappers for SecTrustEvaluate
	var SecTrustEvaluate = new NativeFunction(SecTrustEvaluate_prt, "int", ["pointer", "pointer"]);

	// Hook SecTrustEvaluate
	Interceptor.replace(SecTrustEvaluate_prt, new NativeCallback(function(trust, result) {
		// Show "hit!" message if we are in debugging mode
		if (DEBUG) console.log("[*] SecTrustEvaluate(...) hit!");
		// Call original function
		var osstatus = SecTrustEvaluate(trust, result);
		// Change the result to kSecTrustResultProceed
		Memory.writeU8(result, 1);
		// Return errSecSuccess
		return 0;
	}, "int", ["pointer", "pointer"]));
	// It's done!
	console.log("[*] SecTrustEvaluate(...) hooked. SSL should be pinning disabled.");
}

// Run the script
setImmediate(() => {

    // hook_stat(true);
    // hook_dyld_get_image_name(true)
    // hook_canopenurl(true);
    // hook_fileExistsAtPath(true);
    // hook_writeToFile(true);
    // hook_lstat(true);
    // hook_fork(true);
    // hook_NSClassFromString(true);
    // hook_getenv(true)
    // bypassSSL();
    unpin()
    disable_SecTrustEvaluate();

})
